#include "GLUtils.h"
//#include<sstream>
#include <iostream>
#include <stdlib.h>
#include <cstring>
#include <unistd.h>
#include <GLES2/gl2ext.h>
#include "AutoLog.h"

//#define STB_IMAGE_IMPLEMENTATION-
//#include "stb/stb_image.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb/stb_image_write.h"

//PF_RGBA8 = 0, PF_RGB8, PF_ALPHA8, PF_DEPTH32, PF_DEPTH24Stencil8
static const int c_bytesPixel[] = { 4,3,1,4,4 };
enum PixelFormat
{
    PF_RGBA8 = 0, PF_RGB8, PF_ALPHA8, PF_DEPTH32, PF_DEPTH24Stencil8,PF_NONE
};

GLuint GLUtils::LoadShader(GLenum shaderType, const char *pSource)
{
    GLuint shader = 0;
    LOGE("GLUtils::LoadShader");
        shader = glCreateShader(shaderType);
        if (shader)
        {
            glShaderSource(shader, 1, &pSource, NULL);
            glCompileShader(shader);
            GLint compiled = 0;
            glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
            if (!compiled)
            {
                GLint infoLen = 0;
                glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
                if (infoLen)
                {
                    char* buf = (char*) malloc((size_t)infoLen);
                    if (buf)
                    {
                        glGetShaderInfoLog(shader, infoLen, NULL, buf);
                        LOGE("GLUtils::LoadShader Could not compile shader %d:\n%s\n", shaderType, buf);
                        free(buf);
                    }
                    glDeleteShader(shader);
                    shader = 0;
                }
            }
        }
    LOGE("GLUtils::LoadShader");
	return shader;
}

GLuint GLUtils::CreateProgram(const char *pVertexShaderSource, const char *pFragShaderSource, GLuint &vertexShaderHandle, GLuint &fragShaderHandle)
{
    GLuint program = 0;
    LOGE("GLUtils::CreateProgram");
        vertexShaderHandle = LoadShader(GL_VERTEX_SHADER, pVertexShaderSource);
        if (!vertexShaderHandle) return program;
        fragShaderHandle = LoadShader(GL_FRAGMENT_SHADER, pFragShaderSource);
        if (!fragShaderHandle) return program;

        program = glCreateProgram();
        if (program)
        {
            glAttachShader(program, vertexShaderHandle);
            CheckGLError("glAttachShader");
            glAttachShader(program, fragShaderHandle);
            CheckGLError("glAttachShader");
            glLinkProgram(program);
            GLint linkStatus = GL_FALSE;
            glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);

            glDetachShader(program, vertexShaderHandle);
            glDeleteShader(vertexShaderHandle);
            vertexShaderHandle = 0;
            glDetachShader(program, fragShaderHandle);
            glDeleteShader(fragShaderHandle);
            fragShaderHandle = 0;
            if (linkStatus != GL_TRUE)
            {
                GLint bufLength = 0;
                glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength);
                if (bufLength)
                {
                    char* buf = (char*) malloc((size_t)bufLength);
                    if (buf)
                    {
                        glGetProgramInfoLog(program, bufLength, NULL, buf);
                        LOGE("GLUtils::CreateProgram Could not link program:\n%s\n", buf);
                        free(buf);
                    }
                }
                glDeleteProgram(program);
                program = 0;
            }
        }
    LOGE("GLUtils::CreateProgram");
    LOGE("GLUtils::CreateProgram program = %d", program);
	return program;
}

GLuint GLUtils::CreateProgramWithFeedback(const char *pVertexShaderSource, const char *pFragShaderSource, GLuint &vertexShaderHandle, GLuint &fragShaderHandle, GLchar const **varying, int varyingCount)
{
    GLuint program = 0;
    LOGE("GLUtils::CreateProgramWithFeedback");
        vertexShaderHandle = LoadShader(GL_VERTEX_SHADER, pVertexShaderSource);
        if (!vertexShaderHandle) return program;

        fragShaderHandle = LoadShader(GL_FRAGMENT_SHADER, pFragShaderSource);
        if (!fragShaderHandle) return program;

        program = glCreateProgram();
        if (program)
        {
            glAttachShader(program, vertexShaderHandle);
            CheckGLError("glAttachShader");
            glAttachShader(program, fragShaderHandle);
            CheckGLError("glAttachShader");

            //transform feedback
            glTransformFeedbackVaryings(program, varyingCount, varying, GL_INTERLEAVED_ATTRIBS);
//            GO_CHECK_GL_ERROR();

            glLinkProgram(program);
            GLint linkStatus = GL_FALSE;
            glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);

            glDetachShader(program, vertexShaderHandle);
            glDeleteShader(vertexShaderHandle);
            vertexShaderHandle = 0;
            glDetachShader(program, fragShaderHandle);
            glDeleteShader(fragShaderHandle);
            fragShaderHandle = 0;
            if (linkStatus != GL_TRUE)
            {
                GLint bufLength = 0;
                glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength);
                if (bufLength)
                {
                    char* buf = (char*) malloc((size_t)bufLength);
                    if (buf)
                    {
                        glGetProgramInfoLog(program, bufLength, NULL, buf);
                        LOGE("GLUtils::CreateProgramWithFeedback Could not link program:\n%s\n", buf);
                        free(buf);
                    }
                }
                glDeleteProgram(program);
                program = 0;
            }
        }
    LOGE("GLUtils::CreateProgramWithFeedback");
    LOGE("GLUtils::CreateProgramWithFeedback program = %d", program);
    return program;
}

void GLUtils::DeleteProgram(GLuint &program)
{
    LOGE("GLUtils::DeleteProgram");
    if (program)
    {
        glUseProgram(0);
        glDeleteProgram(program);
        program = 0;
    }
}
#include <map>
std::map<int, std::string> ErrorInfo{
        {0, "NO_ERROR"},
        {1280, "GL_INVALID_ENUM"},
        {1281, "GL_INVALID_VALUE"},
        {1282, "GL_INVALID_OPERATION"},
        {1283, "GL_STACK_OVERFLOW"},
        {1284, "GL_STACK_UNDERFLOW"},
        {1285, "GL_OUT_OF_MEMORY"},
        {1286, "GL_INVALID_FRAMEBUFFER_OPERATION"},
        {1287, "GL_CONTEXT_LOST"}
};
void GLUtils::CheckGLError(const char *pGLOperation)
{
    for (GLuint error = glGetError(); error; error = glGetError())
    {
        LOGE("GLUtils::CheckGLError GL Operation %s() glError (%u) \n", pGLOperation, error);
    }

}

GLuint GLUtils::CreateProgram(const char *pVertexShaderSource, const char *pFragShaderSource) {
    GLuint vertexShaderHandle, fragShaderHandle;
    return CreateProgram(pVertexShaderSource, pFragShaderSource, vertexShaderHandle, fragShaderHandle);
}

bool GLUtils::saveRender(const std::string& tag,int w, int h,const char* func_name, int line){
    std::vector<uint32_t> outputValues(w* h, 0);
    glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, &outputValues[0]);
    return savePicture(tag,w, h,outputValues.data(),std::string(func_name),line);
}
bool GLUtils::savePicture(const std::string& tag,int m_width, int m_height,void *m_pixelData,std::string func_name, int line){

    char pngPath[200];
    int64_t eventTime = std::chrono::steady_clock::now().time_since_epoch().count();
    eventTime=1;
    sprintf(pngPath, "/sdcard/Android/data/com.xingji.filamentengine/files/debug_pic/%lld-%s-%s-%d.png", eventTime,func_name.c_str(), tag.c_str(), line);
    return save(std::string(pngPath), m_width, m_height,m_pixelData, "png");
}

bool GLUtils::save(const std::string& fileName,int m_width, int m_height,void *m_pixelData, const std::string& fileFormat)
{
    bool ret = false;
    if (m_height > 0 && m_width > 0)
    {
        if (fileFormat == "png")
        {
            ret = stbi_write_png(fileName.c_str(), m_width, m_height, c_bytesPixel[PF_RGBA8], m_pixelData, m_width*c_bytesPixel[PF_RGBA8]);
        }
        if (fileFormat == "jpg")
        {
            ret = stbi_write_jpg(fileName.c_str(), m_width, m_height, c_bytesPixel[PF_RGBA8], m_pixelData,95);
        }
    }
    return ret;
}

int GLUtils::getThreadId(){
//    std::stringstream ss;
//    ss<<std::this_thread::get_id();
//    return ss.str();
    return getpid();
}
unsigned int* GLUtils::createTmpPicture(int width,int height){
    unsigned int* mysurface=(unsigned int  *)malloc(height*width*sizeof(unsigned int ));;
    for(int i=0;i<height;i++)
        for(int j=0;j<width;j++){
            unsigned int color=(16<<(i%255))|(8<<(j%255))|((j*i)%255)|0xff000000;
            mysurface[j*width+i]=color;
        }
    return mysurface;
}
unsigned int* GLUtils::createTmpPicture(int width,int height,uint32_t color){
    unsigned int* mysurface=(unsigned int  *)malloc(height*width*sizeof(unsigned int ));;
    for(int i=0;i<height;i++)
        for(int j=0;j<width;j++){
            mysurface[j*width+i]=color;
        }
    return mysurface;
}
unsigned int* GLUtils::createTmpPictureEmpty(int width,int height){
    unsigned int* mysurface=(unsigned int  *)malloc(height*width*sizeof(unsigned int ));
    memset(mysurface,0,height*width*sizeof(unsigned int ));
    return mysurface;
}
GLuint GLUtils::createColorfulTexture(int width, int height) {
    unsigned int* buf=GLUtils::createTmpPicture(width,height);;
    GLuint tex=GLUtils::createTexture( width,  height, buf);
    free(buf);
    return tex;
}
GLuint GLUtils::createTexture(int width, int height,uint32_t color) {
    unsigned int* buf=GLUtils::createTmpPicture(width,height,color);
    GLuint tex=GLUtils::createTexture( width,  height, buf);
    free(buf);
    return tex;
}
GLuint GLUtils::createTexture(int width, int height,void* data) {
    GLuint textureId;
    glActiveTexture(GL_TEXTURE0);
    glGenTextures(1, &textureId);

    glBindTexture(GL_TEXTURE_2D, textureId);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width , height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (uint32_t*)data );

    CHECK_EGL_INVOKE(glTexImage2D);
    return textureId;
}
